"""
(c) 2011 Silvio Silva
Last revision: 2nd April 2011
"""
#!/usr/bin/env python

import os
import datetime
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app
from google.appengine.ext.webapp import util
from google.appengine.api import files
from google.appengine.api import blobstore
from google.appengine.ext import db
import DataModels
import time
import struct
import zlib

ZIP_STORED = 0


class simpleArchiverHeader:
        """ SimpleArchiverHeader take care of archived files headers"""
        def _encodeFilenameFlags(self):
                if isinstance(self.filename, unicode):
                    try:
                        return self.filename.encode('ascii'), self.flag_bits
                    except UnicodeEncodeError:
                        return self.filename.encode('utf-8'), self.flag_bits | 0x800
                else:
                    return self.filename, self.flag_bits

        def __init__(self):
                self.date_time = time.localtime(time.time())[:6]
                self.dosdate = (self.date_time[0] - 1980) << 9 | self.date_time[1] << 5 | self.date_time[2]
                self.dostime = self.date_time[3] << 11 | self.date_time[4] << 5 | (self.date_time[5] // 2)
                self.CRC = 0
                self.compress_size = 0
                self.file_size = 0
                self.extra = ''
                self.filename = ''
                self.create_system = 3
                self.create_version = 20
                self.extract_version = 20
                self.reserved = 0
                self.flag_bits = 0
                self.volume = 0
                self.internal_attr = 0
                self.external_attr = 0
                self.comment = ''
        def get_header(self):
                self.filename, flag_bits = self._encodeFilenameFlags()
                header = struct.pack("<4s2B4HL2L2H", "PK\003\004",
                         self.extract_version, self.reserved, self.flag_bits,
                         self.compress_type, self.dostime, self.dosdate, self.CRC,
                         self.compress_size, self.file_size,
                         len(self.filename), len(self.extra))
                return header + self.filename + self.extra

class simpleArchiver:
        "SimpleArchiver archives files in the blobstore to another file in the blobstore using files API"
        def __init__(self, compression_type=ZIP_STORED, comment=''):
                if compression_type != ZIP_STORED:
                        raise Exception('Only archiving is supported by simpleArchiver')
                self.compression_type=ZIP_STORED
                self.f = None
                self.offset = 0
                self.zip_headers = []
                self.comment = comment
                pass
        
        def open(self, file_name=None, mode='r'):
                if mode != 'r' and mode != 'a':
                        raise Exception('Unsupported mode')
                self.f = files.open(file_name, mode)
                self.filename = file_name
                return self
        
        def add_file_from_blobstore(self,blob_info, comment=''):

                zip_header = simpleArchiverHeader()
                zip_header.filename = blob_info.filename
                zip_header.external_attr = 0600 << 16
                zip_header.compress_size = blob_info.size
                zip_header.compress_type = self.compression_type

                zip_header.file_size = blob_info.size
                zip_header.header_offset = self.offset
                blob_reader = blob_info.open()
                zip_header.CRC = zlib.crc32(blob_reader.read()) & 0xffffffff
                blob_reader.close()
                zip_header.comment = comment
        
                self.zip_headers.append(zip_header)
                try:
                        self.f.write(zip_header.get_header())
                except:
                        self.open(self.filename, 'a')
                        self.f.write(zip_header.get_header())
                self.offset += len(zip_header.get_header())
                del zip_header
        
                blob_reader = blob_info.open()
                count =0
                while count < (blob_info.size / 1000000)+1:
                        cur = blob_reader.tell()
                        try:
                                self.f.write(blob_reader.read(1000000))
                        except:
                                blob_reader.seek(cur)
                                self.open(self.filename, 'a')
                                self.f.write(blob_reader.read(1000000))
                        count +=1
                blob_reader.close()
                self.offset += blob_info.size
        def close(self):
                pos1 = self.offset
                for zinfo in self.zip_headers:
                        dt = zinfo.date_time
                        dosdate = (dt[0] - 1980) << 9 | dt[1] << 5 | dt[2]
                        dostime = dt[3] << 11 | dt[4] << 5 | (dt[5] // 2)
                        extra = []
                        zfilename, flag_bits = zinfo._encodeFilenameFlags()
                        centdir = struct.pack("<4s4B4HL2L5H2L",
                                "PK\001\002", zinfo.create_version,
                                zinfo.create_system, zinfo.extract_version, zinfo.reserved,
                                zinfo.flag_bits, zinfo.compress_type, dostime, dosdate,
                                zinfo.CRC, zinfo.compress_size, zinfo.file_size,
                                len(zinfo.filename), len(zinfo.extra), len(zinfo.comment),
                                0, zinfo.internal_attr, zinfo.external_attr, zinfo.header_offset)
                        try:
                                self.f.write(centdir)
                        except:
                                self.open(self.filename, 'a')
                                self.f.write(centdir)
                        try:
                                self.f.write(zfilename)
                        except:
                                self.open(self.filename, 'a')
                                self.f.write(zfilename)
                        try:
                                self.f.write(zinfo.extra)
                        except:
                                self.open(self.filename, 'a')
                                self.f.write(zinfo.extra)
                        try:
                                self.f.write(zinfo.comment)
                        except:
                                self.open(self.filename, 'a')
                                self.f.write(zinfo.comment)
                        self.offset += len(centdir) + len(zfilename) + len(zinfo.extra) + len(zinfo.comment)
                pos2 = self.offset
                centDirSize = pos2 - pos1
                centDirOffset = pos1

                endrec = struct.pack("<4s4H2LH", "PK\005\006",
                                 0, 0, len(self.zip_headers),len(self.zip_headers),
                                 centDirSize, centDirOffset, len(self.comment))
                
                try:
                        self.f.write(endrec)        
                except:
                        self.open(self.filename, 'a')
                        self.f.write(endrec)
                try:
                        self.f.write(self.comment)
                except:
                        self.open(self.filename, 'a')
                        self.f.write(self.comment)
                self.f.close()

class CompressGallery(webapp.RequestHandler):
        def post(self, gallery_key):
                """Read an image from a gallery, zip and append it to the gallery zip file
                Args:
                    blob_key: current file, '0' for none
                    filename: gallery zip file on blobstore
                    gallery_key: DataGallery Entity key
                    image_offset: image offset on DataGallery.images (query)
                Returns:
                    A string with data read.
                """
                g = DataModels.DataGallery.get(db.Key(gallery_key))
                if g.archive_blob != None:
                        return
                filename = files.blobstore.create(_blobinfo_uploaded_filename=g.name + '.zip')
                """ Raising an error here on the second task 'ExistenceError' """
                if g.name == None:
                        f = simpleArchiver().open(filename, 'a')
                else:
                        f = simpleArchiver(comment=g.name).open(filename, 'a')
                for i in g.images:
                        if i.deleted == False:
                                if i.name == None:
                                        f.add_file_from_blobstore(i.blob_key)
                                else:
                                        f.add_file_from_blobstore(i.blob_key, i.name)
                f.close()
                files.finalize(filename)
                

                g.archive_blob = files.blobstore.get_blob_key(filename)
                g.put()
                self.response.out.write("ok")

                

def main():
    application = webapp.WSGIApplication([
                                            ('/tasks/galleries/compress/([^/]+)?', CompressGallery)
                                         ],
                                         debug=True)
    util.run_wsgi_app(application)


if __name__ == '__main__':
    main()